package top.mcwebsite.playandroid.lib.servicemanager.processor

import com.google.devtools.ksp.KSTypeNotPresentException
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview
import top.mcwebsite.playandroid.lib.servicemanager.core.annoation.ServiceImpl
import java.io.PrintStream
import kotlin.Exception
import kotlin.math.abs

class ServiceImplSymbolProcessProvider : SymbolProcessorProvider {
    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
        return ServiceImplSymbolProcessor(
            environment.logger, environment.codeGenerator, environment.options
        )
    }

    class ServiceImplSymbolProcessor(
        private val logger: KSPLogger,
        private val codeGenerator: CodeGenerator,
        val options: Map<String, String>
    ) : SymbolProcessor {

        companion object {
           private val SERVICEIMPL_CLASS_NAME = ServiceImpl::class.qualifiedName!!
        }


        override fun process(resolver: Resolver): List<KSAnnotated> {
            logger.warn("ServiceImplSymbolProcessProvider start handler")
            val symbol = resolver.getSymbolsWithAnnotation(SERVICEIMPL_CLASS_NAME)
            val elements = symbol.filterIsInstance<KSClassDeclaration>().toList()
            if (elements.isNotEmpty()) {
                logger.info(">>> ServiceImplSymbolProcessProvider init. <<<")
                try {
                    parseServiceImpl(elements)
                } catch (e: Exception) {
                    logger.exception(e)
                }
            }
            return emptyList()
        }

        private fun parseServiceImpl(elements: List<KSClassDeclaration>) {
            logger.info(">>> Found services, size is " + elements.size + " <<<")
            val serviceMap = HashMap<String, MutableList<ServiceImplMeta>>()
            for (element in elements) {
                val serviceImplMeta = extractServiceImplMeta(element)
                if (serviceMap.contains(serviceImplMeta.serviceName)) {
                    serviceMap[serviceImplMeta.serviceName]!!.add(serviceImplMeta)
                } else {
                    serviceMap[serviceImplMeta.serviceName] = mutableListOf(serviceImplMeta)
                }
            }
            generateProviderFile(serviceMap)
        }

        @OptIn(KotlinPoetKspPreview::class)
        private fun generateProviderFile(serviceMap: Map<String, MutableList<ServiceImplMeta>>) {
            if (serviceMap.isEmpty()) {
                return
            }
            val className = PREFIX_SERVICE_PROVIDER + abs(serviceMap.keys.first().hashCode())
            var ps: PrintStream? = null
            try {
                ps = PrintStream(codeGenerator.createNewFile(Dependencies.ALL_FILES, PACKAGE, className))
                ps.println("package $PACKAGE")
                ps.println()
                ps.println("import kotlin.reflect.KClass")
                ps.println()
                ps.print("""
                    /**
                     * Code Generated, do not modifier!!!
                     * generated by KSP
                     * JDK version is ${System.getProperty("java.version")}
                     */
                """.trimIndent())
                ps.println()
//                ps.println("@androidx.annotation.Keep")
                ps.println("class $className : top.mcwebsite.playandroid.lib.servicemanager.core.IServiceProvider {")
                ps.println()
                var tab = TAB
                ps.println("${tab}override fun getServiceImplBeans(): Map<KClass<*>, List<top.mcwebsite.playandroid.lib.servicemanager.core.ServiceImplBean<*>>> {")
                tab += TAB
                ps.println("${tab}val map = mutableMapOf<KClass<*>, List<top.mcwebsite.playandroid.lib.servicemanager.core.ServiceImplBean<*>>>()")
                for (entry in serviceMap) {
                    ps.println("${tab}map[${entry.key}::class] = listOf(",)
                    tab += TAB
                    for (serviceImpl in entry.value) {
                        ps.println("${tab}top.mcwebsite.playandroid.lib.servicemanager.core.ServiceImplBean<${serviceImpl.serviceImplName}>(")
                        tab += TAB
                        ps.println("${tab}serviceImplClass = ${serviceImpl.serviceImplName}::class,")
                        ps.println("${tab}isSingleton = ${serviceImpl.isSingleton},")
                        ps.println("${tab}factory = object : top.mcwebsite.playandroid.lib.servicemanager.core.ServiceFactory<${serviceImpl.serviceImplName}> {")
                        tab += TAB
                        ps.println("${tab}override fun create(): ${serviceImpl.serviceImplName} {")
                        tab += TAB
                        ps.println("${tab}return ${serviceImpl.serviceImplName}()")
                        tab = tab.substring(4)
                        ps.println("${tab}}")
                        tab = tab.substring(4)
                        ps.println("${tab}},")
                        tab = tab.substring(4)
                        ps.println("${tab}),")
                    }
                    tab = tab.substring(4)
                    ps.println("${tab})")
                }
                ps.println("${tab}return map")
                tab = tab.substring(4)
                ps.println("${tab}}")
                ps.println("}")
                ps.flush()
            } catch (e: Exception) {
                e.printStackTrace()
            } finally {
                ps?.close()
            }
//            val parameterName = "serviceImpls"
//            val parameterSpec = ParameterSpec.builder(
//                parameterName, MUTABLE_MAP.parameterizedBy(
//                    String::class.asClassName(), ServiceImplBean::class.asClassName()
//                ).copy(nullable = true)
//            ).build()
//
//            val loadInfoFunSpecBuilder: FunSpec.Builder =
//                FunSpec.builder("loadInfo")
//                    .addParameter(parameterSpec)
//
//            loadInfoFunSpecBuilder.addStatement("if($parameterName == null) { return }")
//            for (entry in serviceMap) {
//                for (serviceImpl in entry.value) {
//                    loadInfoFunSpecBuilder.addStatement(
//                        "$parameterName.put(%S, %T(serviceImplClassName = %S, isSingleton = ${serviceImpl.isSingleton}," +
//                                " serviceFactory = object : ServiceFactory<%S> {" +
//                                "))",
//                        entry.key,
//                        ServiceImplBean::class.java,
//                        serviceImpl.serviceImplName,
//                    )
//                }
//            }
//
//            val dependencies = mutableSetOf<KSFile>()
//
//
//            val rootClassName = "ServiceImplProvider" + abs(serviceMap.keys.first().hashCode())
//
//            val file = FileSpec.builder("top.mcwebsite.playandroid.lib.serviceManager", rootClassName).addType(
//                TypeSpec.classBuilder(
//                    ClassName(
//                        "top.mcwebsite.playandroid.lib.serviceManager", rootClassName
//                    )
//                ).addKdoc("DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY ServiceManager.")
//                    .addFunction(loadInfoFunSpecBuilder.build()).build()
//            ).build()
//            file.writeTo(codeGenerator, true, dependencies)
//            logger.info(">>> Generated root, name is $rootClassName <<<")
        }

        @OptIn(KspExperimental::class, KotlinPoetKspPreview::class)
        private fun extractServiceImplMeta(element: KSClassDeclaration): ServiceImplMeta {
            val qualifiedName = element.qualifiedName?.asString()
                ?: error("local variable can not be annotated with @ServiceImpl")
            val serviceImpl = element.getAnnotationsByType(ServiceImpl::class).firstOrNull()
                ?: error("$qualifiedName must annotated with @ServiceImpl")
            val serviceName = try {
                serviceImpl.value.qualifiedName ?: error("出错了")
            } catch (e: KSTypeNotPresentException) {
                e.ksType.declaration.qualifiedName?.asString() ?: error("出错了")
            }
            return ServiceImplMeta(
                serviceName = serviceName,
                serviceImplName = qualifiedName,
                isSingleton = serviceImpl.isSingleton
            )
        }

    }
}